import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * @author dc327
 */
public class VendingMachineTest {
	VendingMachine machine1;
	static VendingMachineItem item1;
	
	/**
	 * Initialize the vending machine item prior to starting
	 * the tests on the vending machine so that it is clear
	 * what is causing any of the errors
	 * 
	 * @throws Exception
	 */
	@BeforeClass
	public static void setUpItem() throws Exception {
		item1 = new VendingMachineItem("Soda", 1.00);
	}
	
	/**
	 * Set up the vending machine to a blank state.
	 * Should have 4 columns and an initial balance
	 * of 0
	 * 
	 * @throws java.lang.Exception
	 */
	@Before
	public void setUp() throws Exception {
		machine1 = new VendingMachine();
	}

	/**
	 * Confirm the initial balance of the vending machine
	 * is 0
	 *
	 * @throws Exception the exception
	 */
	@Test
	public void testInitialBalance() throws Exception {
		assertEquals(0, machine1.getBalance(), 0.0001);
	}
	
	/**
	 * Test adding an item to each slot in the 
	 * vending machine
	 *
	 * @throws Exception the exception
	 */
	@Test
	public void testAddItem() throws Exception {
		machine1.addItem(item1, "A");
		machine1.addItem(item1, "B");
		machine1.addItem(item1, "C");
		machine1.addItem(item1, "D");
	}
	
	/**
	 * Test adding an item to a non-empty slot.
	 *
	 * @throws Exception the exception
	 */
	@Test(expected=VendingMachineException.class)
	public void testAddItemNonEmptySlot() throws Exception {
		machine1.addItem(item1, "A");
		machine1.addItem(item1, "A");
	}
	
	/**
	 * Test adding an item to a non-existant slot.
	 *
	 * @throws Exception the exception
	 */
	@Test(expected=VendingMachineException.class)
	public void testAddItemToNonExistantSlot() throws Exception {
		machine1.addItem(item1, "E");
	}
	
	/**
	 * Test adding an item to a slot with code null in the 
	 * vending machine
	 *
	 * @throws Exception the exception
	 */
	@Test
	public void testAddItemToNullSpot() throws Exception {
		machine1.addItem(item1, null);
	}
	
	/**
	 * Test removing an item from each slot in the 
	 * vending machine. Forced to populate slots 
	 * prior to removing an item so must call the
	 * testAddItem method
	 *
	 * @throws Exception the exception
	 */
	@Test
	public void testRemoveItem() throws Exception {
		testAddItem();
		
		machine1.removeItem("A");
		machine1.removeItem("B");
		machine1.removeItem("C");
		machine1.removeItem("D");
	}
	
	/**
	 * Test removing an item from an empty slot.
	 *
	 * @throws Exception the exception
	 */
	@Test(expected=VendingMachineException.class)
	public void testRemoveItemFromEmptySlot() throws Exception {
		machine1.removeItem("A");
	}
	
	/**
	 * Test removing an item from a non-existant slot.
	 *
	 * @throws Exception the exception
	 */
	@Test(expected=VendingMachineException.class)
	public void testRemoveItemFromNonExistantSlot() throws Exception {
		machine1.removeItem("E");
	}
	
	/**
	 * Test for inserting a valid amount of money into 
	 * the machine.
	 *
	 * @throws Exception the exception
	 */
	@Test
	public void testInsertMoney() throws Exception {
		machine1.insertMoney(20.00);
	}
	
	/**
	 * Test if the system will throw an error when a negative 
	 * amount of money is inserted.
	 *
	 * @throws Exception the exception
	 */
	@Test(expected=VendingMachineException.class)
	public void testInsertNegativeMoney() throws Exception {
		machine1.insertMoney(-20.00);
	}
	
	/**
	 * Test getting a balance after money has been inserted 
	 * into the machine. Use the testInsertMoney method to 
	 * verify that the counting mechanism is working 
	 * correctly
	 *
	 * @throws Exception the exception
	 */
	@Test
	public void testGetBalance() throws Exception {
		testInsertMoney();
		assertEquals(20.00, machine1.getBalance(), 0.01);
	}
	
	/**
	 * Test a valid transaction. The machine is populated
	 * and you have the required funds.
	 *
	 * @throws Exception the exception
	 */
	@Test
	public void testMakePurchase() throws Exception {
		testAddItem();
		testInsertMoney();
		boolean result = machine1.makePurchase("A");
		assertTrue(result);
	}
	
	/**
	 * Test an invalid transaction. Making a purchase from an empty 
	 * machine.
	 *
	 * @throws Exception the exception
	 */
	@Test
	public void testMakePurchaseFromEmptyMachine() throws Exception {
		testInsertMoney();
		boolean result = machine1.makePurchase("A");
		assertFalse(result);
	}
	
	/**
	 * Test an invalid transaction. Making a purchase without the
	 * required funds
	 *
	 * @throws Exception the exception
	 */
	@Test
	public void testMakePurchaseWithoutFunds() throws Exception {
		testAddItem();
		boolean result = machine1.makePurchase("A");
		assertFalse(result);
	}
	
	/**
	 * Test the base case of returning change when there is none
	 *
	 * @throws Exception the exception
	 */
	@Test
	public void testReturnChange() throws Exception {
		double result = machine1.returnChange();
		assertEquals(0.0, result, 0.01);
	}
	
	/**
	 * Test the case of returning change when change > 0
	 *
	 * @throws Exception the exception
	 */
	@Test
	public void testReturnChangeValue() throws Exception {
		testInsertMoney();
		double result = machine1.returnChange();
		assertEquals(20.00, result, 0.01);
	}
	
	/**
	 * Test whether the balance is cleared after the return change
	 * method is called.
	 *
	 * @throws Exception the exception
	 */
	public void testGetBalanceAfterReturnChange() throws Exception {
		testReturnChangeValue();
		double result = machine1.getBalance();
		assertEquals(0.0, result, 0.01);
	}
}


